/* ******************************************************************************
 *
 *       Copyright 2008-2010 Hans Dijkema
 *
 *   JRichTextEditor is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as 
 *   published by the Free Software Foundation, either version 3 of 
 *   the License, or (at your option) any later version.
 *
 *   JRichTextEditor is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with JRichTextEditor.  If not, see <http://www.gnu.org/licenses/>.
 *   
 * ******************************************************************************/

package nl.dykema.jxmlnote.widgets;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.Vector;
import java.util.WeakHashMap;

import javax.swing.Icon;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.Position;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

import nl.dykema.jxmlnote.document.XMLNoteDocument;
import nl.dykema.jxmlnote.document.XMLNoteImageIcon;
import nl.dykema.jxmlnote.document.XMLNoteMark;
import nl.dykema.jxmlnote.exceptions.BadStyleException;
import nl.dykema.jxmlnote.exceptions.DefaultXMLNoteErrorHandler;
import nl.dykema.jxmlnote.interfaces.MarkMarkupProvider;
import nl.dykema.jxmlnote.styles.XMLNoteStyleConstants;


public class XMLNoteEditorKit extends StyledEditorKit {

	private static final long serialVersionUID = 1L;
	
	private JXMLNotePane _pane=null;
	
	private WeakHashMap<Icon,Icon>  	_iconMap;
	private WeakHashMap<Icon,Double>	_zoomMap;
	
	public Icon getZoomedInstance(Icon c) {
		Double z=_zoomMap.get(c);
		Double zf=_pane.getZoomFactor();
		if (z==null || !z.equals(zf)) {
			_iconMap.put(c, deriveIconForZoom(c));
			_zoomMap.put(c,zf);
		} else if (z!=null) {
			if (c instanceof XMLNoteImageIcon) {
				XMLNoteImageIcon xicn=(XMLNoteImageIcon) c;
				Icon cc=_iconMap.get(c);
				if (cc!=null && cc instanceof XMLNoteImageIcon) {
					XMLNoteImageIcon nicn=(XMLNoteImageIcon) cc;
					if (!nicn.equalOrig(xicn)) {
						_iconMap.put(c, deriveIconForZoom(c));
						_zoomMap.put(c, zf);
					}
				}
			}
		}
		return _iconMap.get(c);
	}
	
	public Icon deriveIconForZoom(Icon c) {
        if (c instanceof XMLNoteImageIcon) {
        	XMLNoteImageIcon xicn=(XMLNoteImageIcon) c;
        	XMLNoteImageIcon nicn=xicn.getZoomedInstance(_pane,new XMLNoteImageIcon.LoadedListener() {
        		public void loaded() {
        			SwingUtilities.invokeLater(new Runnable() {
        				public void run() { _pane.repaint(); }
        			});
        		}
        	});
        	return nicn;
		} else {
			return c;
        }
	}

	public ViewFactory getViewFactory() {
		if (_pane==null) {
			DefaultXMLNoteErrorHandler.exception(new Exception("Unexpected: get of viewfactory before install of pane"));
			return null;
		} else {
	        return new XMLNoteViewFactory(_pane,this);
		}
	}
	
	public Document createDefaultDocument() {
		try {
			return new XMLNoteDocument();
		} catch (BadStyleException e) {
			DefaultXMLNoteErrorHandler.exception(e);
			return null;
		}
	}
	
	public void install(JEditorPane p) {
		if (p instanceof JXMLNotePane) {
			_pane=(JXMLNotePane) p;
		} else {
			DefaultXMLNoteErrorHandler.exception(new Exception("Unexpected: install of something else than a JXMLNotePane"));
		}
		super.install(p);
	}
	
	public void deinstall(JEditorPane p) {
		_pane=null;
		super.deinstall(p);
	}
	
	public XMLNoteEditorKit(JXMLNotePane p) {
		_pane=p;
		_iconMap=new WeakHashMap<Icon,Icon>();
		_zoomMap=new WeakHashMap<Icon,Double>();
	}
}

class XMLNoteViewFactory implements ViewFactory {

	private JXMLNotePane 		_pane;
	private XMLNoteEditorKit 	_kit;
	
	public View create(Element elem) {
		String kind = elem.getName();
		if (kind != null) {
			if (kind.equals(AbstractDocument.ContentElementName)) {
				return new MyLabelView(elem,_pane);
			}
			else if (kind.equals(AbstractDocument.ParagraphElementName)) {
				return new MyParagraphView(elem,_pane);
			}
			else if (kind.equals(AbstractDocument.SectionElementName)) {
				//return new ScaledView(elem, View.Y_AXIS);
				return new BoxView(elem,View.Y_AXIS);
			}
			else if (kind.equals(StyleConstants.ComponentElementName)) {
				return new ComponentView(elem);
			}
			else if (kind.equals(StyleConstants.IconElementName)) {
				//Element q=getScaledIcon(elem);
				return new MyIconView(elem,_kit);
			}
		}

		// default to text display
		return new LabelView(elem);
	}
	
	
	
	public JXMLNotePane getPane() {
		return _pane;
	}
	
	public XMLNoteViewFactory(JXMLNotePane p,XMLNoteEditorKit kit) {
		_pane=p;
		_kit=kit;
	}

}

class MyIconView extends View {

		private XMLNoteEditorKit 	_kit;
		private Icon				_icn;
		private Icon				_orig;
	
	    public MyIconView(Element elem,XMLNoteEditorKit kit) {
	        super(elem);
	        _kit=kit;
	        AttributeSet attr = elem.getAttributes();
	        _orig=StyleConstants.getIcon(attr);
	        _icn=_kit.getZoomedInstance(_orig);
	    }

	    public void paint(Graphics g, Shape a) {
	        Rectangle alloc = a.getBounds();
	        _icn=_kit.getZoomedInstance(_orig);
	        _icn.paintIcon(getContainer(), g, alloc.x, alloc.y);
	    }

	    public float getPreferredSpan(int axis) {
	    	_icn=_kit.getZoomedInstance(_orig);
	        switch (axis) {
	        case View.X_AXIS:
	            return _icn.getIconWidth();
	        case View.Y_AXIS:
	            return _icn.getIconHeight();
	        default:
	            throw new IllegalArgumentException("Invalid axis: " + axis);
	        }
	    }

	    public float getAlignment(int axis) {
	        switch (axis) {
	        case View.Y_AXIS:
	            return 1;
	        default:
	            return super.getAlignment(axis);
	        }
	    }

	    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
	        int p0 = getStartOffset();
	        int p1 = getEndOffset();
	        if ((pos >= p0) && (pos <= p1)) {
	            Rectangle r = a.getBounds();
	            if (pos == p1) {
	                r.x += r.width;
	            }
	            r.width = 0;
	            return r;
	        }
	        throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos);
	    }

	    public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
	        Rectangle alloc = (Rectangle) a;
	        if (x < alloc.x + (alloc.width / 2)) {
	            bias[0] = Position.Bias.Forward;
	            return getStartOffset();
	        }
	        bias[0] = Position.Bias.Backward;
	        return getEndOffset();
	    }

}

class MyParagraphView extends ParagraphView {

	JXMLNotePane _pane;
	Element		 _par;
	TabSet       _tabs;
	float        _zoom=-1.0f;
	
	protected TabSet getTabSet() {
		TabSet ts=StyleConstants.getTabSet(_par.getAttributes());
		if (ts!=null) {
			
			float zoom=(float) _pane.getZoomFactor();
			if (zoom!=_zoom || _tabs==null) {
				TabStop[] stp=new TabStop[ts.getTabCount()];
				int i,N;
				for(i=0,N=ts.getTabCount();i<N;i++) {
					TabStop s=ts.getTab(i);
					stp[i]=new TabStop(s.getPosition()*zoom,s.getAlignment(),s.getLeader());
				}
				_zoom=zoom;
				_tabs=new TabSet(stp);
			}
			return _tabs;
		} else {
			return null;
		}
	}
	
	public MyParagraphView(Element p,JXMLNotePane pn) {
		super(p);
		_pane=pn;
		_par=p;
	}
}

class MyLabelView extends LabelView {
	
	JXMLNotePane _pane;
	Element		 _elem;
	Color		 _textColor=null;
	
	private Font	_myFont=null;
	private float   _myPoints=-1.0f;
	private double  _myZoom=-1.0;
	private Font    _myDerived=null;
	
	public Font getFont() {
		Font f=super.getFont();
		float ps=f.getSize2D();
		double zoom=_pane.getZoomFactor();
		if (_myFont==null || ps!=_myPoints || zoom!=_myZoom || _myDerived==null) {
			_myFont=f;
			_myPoints=ps;
			_myZoom=zoom;
			_myDerived=_myFont.deriveFont((float) (ps*_myZoom));
		}
		return _myDerived;
	}
	
	// override the foreground colour, if (part of) this text is Marked 
	protected void setPropertiesFromAttributes() {
		super.setPropertiesFromAttributes();
		
	}
	
	public Color getForeground() {
		if (_elem!=null) {
			AttributeSet set=_elem.getAttributes();
			Vector<XMLNoteMark> marks=XMLNoteStyleConstants.getMarks(set);
			if (marks!=null && !marks.isEmpty()) {
				// catch the first mark. See documentation in XMLNoteDocument.
				XMLNoteMark mark=marks.get(0);
				MarkMarkupProvider prov=_pane.getMarkMarkupProviderMaker().create(mark.id(), mark.markClass());
				_textColor=prov.textColor(mark);
			} else {
				_textColor=null;
			}
		}
		
		if (_textColor!=null) {
			return _textColor;
		} else {
			return super.getForeground();
		}
	}
	
	public MyLabelView(Element e,JXMLNotePane pane) {
		super(e);
		_elem=e;
		_pane=pane;
	}
}

/*class MyIconView extends IconView {
	
	JXMLNotePane _pane;
	
	public static Element deriveIconForZoom(Element e,JXMLNotePane p) {
		return p.getXMLNoteDoc().deriveIconForZoom(e,p);
	}
	
	public MyIconView(Element e,JXMLNotePane pane) {
		_pane=pane;
	}
}*/

class ScaledView extends BoxView {
   
	public ScaledView(Element elem, int axis) {
        super(elem, axis);
    }

    public double getZoomFactor() {
    	XMLNoteViewFactory v=(XMLNoteViewFactory) super.getViewFactory();
        double scale = v.getPane().getZoomFactor();
        return scale;
    }

    public void paint(Graphics g, Shape allocation) {
        Graphics2D g2d = (Graphics2D) g;
        double zoomFactor = getZoomFactor();
        AffineTransform old = g2d.getTransform();
        g2d.scale(zoomFactor, zoomFactor);
        super.paint(g2d, allocation);
        g2d.setTransform(old);
    }

    public float getMinimumSpan(int axis) {
        float f = super.getMinimumSpan(axis);
        f *= getZoomFactor();
        return f;
    }

    public float getMaximumSpan(int axis) {
        float f = super.getMaximumSpan(axis);
        f *= getZoomFactor();
        return f;
    }

    public float getPreferredSpan(int axis) {
        float f = super.getPreferredSpan(axis);
        f *= getZoomFactor();
        return f;
    }

    protected void layout(int width, int height) {
    	double zoom=getZoomFactor();
        super.layout((int) (((double) width)/zoom),(int) (((double) height)/zoom));
    }

    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
    	Rectangle alloc;
    	double zoomFactor = getZoomFactor();
        alloc = a.getBounds();
        Shape s = super.modelToView(pos, alloc, b);
        alloc = s.getBounds();
        double x=alloc.getX()*zoomFactor;
        double y=alloc.getY()*zoomFactor;
        double w=alloc.getWidth()*zoomFactor;
        double h=alloc.getHeight()*zoomFactor;
        alloc.x=(int) Math.round(x);
        alloc.y=(int) Math.round(y);
        alloc.width=(int) Math.round(w);
        alloc.height=(int) Math.round(h);
        return alloc;
    }

    public int viewToModel(float x, float y, Shape a,Position.Bias[] bias) {
        double zoomFactor = getZoomFactor();
        Rectangle alloc = a.getBounds();
        x /= zoomFactor;
        y /= zoomFactor;
        double xx=alloc.getX()/zoomFactor;
        double yy=alloc.getY()/zoomFactor;
        double w=alloc.getWidth()/zoomFactor;
        double h=alloc.getHeight()/zoomFactor;
        //alloc.x /= zoomFactor;
        //alloc.y /= zoomFactor;
        //alloc.width /= zoomFactor;
        //alloc.height /= zoomFactor;
        alloc.x=(int) Math.round(xx);
        alloc.y=(int) Math.round(yy);
        alloc.width=(int) Math.round(w);
        alloc.height=(int) Math.round(h);

        return super.viewToModel(x, y, alloc, bias);
    }

}

